package gbench.sandbox.jdbc;

import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.tree.LittleTree.TUPLES;
import static gbench.common.tree.LittleTree.trycatch2;
import static gbench.common.tree.LittleTree.IRecord.L;
import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.IRecord.kv2dbl;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summarizingDouble;

import java.io.File;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;

import gbench.common.fs.FileSystem;
import gbench.common.matlib.MatlibCanvas.XVec;
import gbench.common.matlib.data.DataReader.DFrame;
import gbench.common.matlib.rdd.BasePair;
import gbench.common.matlib.rdd.PVec;
import gbench.common.tree.LittleTree;
import gbench.common.tree.LittleTree.IRecord;
import gbench.common.tree.LittleTree.SQL;
import gbench.common.tree.LittleTree.Term;
import gbench.common.tree.LittleTree.TrieNode;
import gbench.commonApp.data.DataApp;
import gbench.sandbox.jdbc.FinanceAccounting;
import gbench.sandbox.jdbc.YmlEngine;

/**
 * 
 * @author Administrator
 *
 */
class FinanceAccounting {
    
    /**
     * 
     */
    public FinanceAccounting(){
        this.initialize();
    }
    
    /**
     * 
     * @return
     */
    public FinanceAccounting initialize() {
        final var url = "jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=True";
        final var dataApp = new DataApp() {}.reload2(REC("url", url)); // dataApp
        final var r = REC("id", 1, "name", "科目名称", "amount", 10d);
        final var createsqls = SQL.of("t_cache", r).createTable();
        final var rb = IRecord.rb(r);
        final var ar = new AtomicReference<DFrame>();
        dataApp.withTransactionX(DFrame.dfmclc, sess -> {
            TUPLES(createsqls).filter(e -> e._1() != 1).map(e -> e._2())
                .forEach(trycatch2(sess::sqlexecute));// 数据表重新建立 
            XVec.OF("固定资产—××设备,库存现金,发出产品,主营业务成本,实收资本—张三,持有至到期投资－利息调整".split(","))
                .fmap((i, name) -> rb.get(i, name, 1))
                .fmap(e->SQL.of("t_cache", e).insert())
                .forEach(LittleTree.trycatch3(sess::sqlexecute));
            final var cache = sess.sql2u("select * from t_cache");
            ar.set(cache);
        });
        final var dfm = ar.get();
        final var map = dfm
                .rowsS(o -> BasePair.bp(o.str("name"), o.dbl("amount")))
                .collect(PVec.mapclc());
        this.dataCache = map;
        println(this.dataCache);
        return this;
    }
    
    /**
     * 会计策略
     * 
     * @param file 科技策略的文件
     * @return
     */
    public List<IRecord> policies(final String file){
        
        final var root = new YmlEngine().yml(file);
        final var rb = IRecord.rb("drcr,name");
        final List<IRecord> accounting_policies = 
            root.childrenS().map(e0 -> {
                return REC( // 分录
                     "name",e0.value(),
                     "entries",e0.childrenS() // 科目
                         .map(e1 -> e1.value(e -> e.split("\\s+")))
                         .map(rb::get)
                         .collect(Collectors.toList())
                ); // REC 
            }).peek(e -> {
                println(e);
            }).collect(Collectors.toList());
        
        return accounting_policies;
    }
    
    /**
     * 借贷记账法
     * @param accounting_policies [(name,entries:[(drcr,name)])]
     * @return
     */
    public BiFunction<String,IRecord,Stream<IRecord>> double_entries(final List<IRecord> accounting_policies){
        
        final var policies = accounting_policies.stream()
                .map(e -> BasePair.bp(e.str("name"), e.recs("entries")))
                .collect(PVec.pvec2clc()).toHashMap();
        
        final Function<String, Double> acct_balance = name -> {
            final var value = this.dataCache.compute(name, (k, v) -> Optional.ofNullable(v).orElse(0d));
            return DRCR_ADJUST.equals(name) ? Double.MAX_VALUE : value;
        };
        final BiFunction<String,IRecord,Stream<IRecord>> accounting_method = (policy_name,sess)->{
            final var policy = policies.get(policy_name);
            final Double balance = sess.stream().map(kv2dbl).collect(summarizingDouble(e->e)).getSum();
            final var drcr2entries = policy.stream().collect(groupingBy(e -> e.i4("drcr")));
            final var primary_entries = Stream.of(1,-1).map(drcr2entries::get).flatMap(List::stream);
            final var secondary_entries = drcr2entries.get(0);
            final var ll = new LinkedList<IRecord>();
            
            if (balance.intValue() != 0) { 
                final var drcr = (balance > 0 ? 1d : -1d) * -1d; // 注意需要使用浮点数
                var total_amount = Math.abs(balance);
                for (final var e : secondary_entries) {
                    final var acct_name = e.str("name");
                    final var acct_amount = acct_balance.apply(acct_name);
                    final var amount = total_amount > acct_amount ? acct_amount : total_amount;
                    final var entry = e.derive("drcr", drcr, "value", drcr * amount);
                    if (amount <= 0) continue; // 非账户余额值不予处理
                    ll.add(entry);
                    if ((total_amount -= amount) <= 0) {
                        break;
                    } // if
                } // for
            } // if
            
            primary_entries
                .map(e -> e.derive( //
                    "amount", sess.get(e.str("name")), // 提取金额
                    "drcr", e.dbl("drcr")) // 借贷方向数字化
                ).forEach(ll::add);
            
            return ll.stream().sorted(IRecord.cmp("drcr",false));
        };
        
        return accounting_method;
    }
    
    private Map<String,Double> dataCache = new HashMap<String,Double>();
    final static String DRCR_ADJUST = "DRCR调整";
}

/**
 * 
 * @author Administrator
 *
 */
class YmlEngine {
    
    /**
     * yml 的格式解析
     * 
     * @param <U>      属性值类型
     * @param bpStream 数据流 [(String,u)]
     * @param cons     回调函数 (node,u) -> {}
     * @return TrieNode 根节点的值 为 null
     */
    public <U> TrieNode<String> yml(final Stream<BasePair<String, U>> bpStream,
            final BiConsumer<TrieNode<String>, U> cons) {
        
        final BiFunction<String, String, String> merge = (a, b) -> Term.FT("$0,$1", a, b.strip());
        final Function<AtomicReference<String>, Function<String, String>> continue_line = a_pre_lines -> e -> {
            final var line = e.replaceAll("[\\\\]*$", ""); // 去除尾部的 续行 符号 \
            if (e.endsWith("\\")) { // 包含有 下一行
                a_pre_lines.updateAndGet(s -> { // 设置当前行
                    return s == null ? line : merge.apply(s, line);
                });
                return null; // 返回空值 用以表名 这是一个未完成的行，需要后面的 处理函数 给与过滤掉 这一null行。
            } else { // 没有续行符
                final var pre_lines = a_pre_lines.get(); // 获取前一行数据
                if (pre_lines == null) { // 没有前导行
                    return line;
                } else { // 包含前导行
                    a_pre_lines.set(null); // 清空前一行数据
                    return merge.apply(pre_lines, line);
                } // if
            } // if
        }; // 续行函数 continue_line
        final var indented_lines = bpStream
                .map(e -> BasePair.bp(continue_line.apply(new AtomicReference<String>()).apply(e._1()), e._2())) // 续行计算
                .filter(bp -> bp._1() != null && !bp._1().matches("\\s*")) // 去除空行 和 null 值
                .collect(Collectors.toList());
        final var n = indented_lines.size(); // 行数目
        final var ident_pattern = Pattern.compile("^\\s*"); // 缩进的pattern
        final var ident_entries = new TreeMap<Integer, String>(); // TreeMap 键值排序，层级结构
        final var trie = new TrieNode<String>(rootName, LinkedHashMap::new); // Trie 根结构
        
        for (int i = 0; i < n; i++) { // 逐行处理
            final var indented_line = indented_lines.get(i);
            final var line = indented_line._1();
            final var matcher = ident_pattern.matcher(line); // 缩进侦测
            
            if (matcher.find()) { // 满足缩进格式
                final var indent = matcher.group().replaceAll("\t", " ".repeat(this.indent_unit)); // 获取缩进距离，替换 \t 为 空格
                final int n_indent = indent.length() % this.indent_unit == 0 ? indent.length() // 缩进长度
                        : this.indent_unit * ((int) (indent.length() / this.indent_unit + 1)); // 向上取整
                final var level = n_indent / this.indent_unit; // 当前数据项目的的层级深度, 每个tab 键名 采用 this.ident_unit 个 空格进行划分
                ident_entries.put(level, line.strip()); // 记录键值的层级

                if (n_indent % this.indent_unit != 0) { // 提取非格式化部分
                    System.err.println(Term.FT("error: no: $0\t level:$1\t remainder:$2 \t line:$3", i + 1, level,
                            n_indent % this.indent_unit, line));
                } else {
                    final var flds = new LinkedList<String>(); // 字段列表
                    ident_entries.entrySet() // 获取行项目
                            .stream() // 层级的排序 按照 层级深度进行排序, 层级越深 表示 数据越细节
                            .filter(e -> e.getKey() <= level) // 提取 当前层级 以上的所有 摘要层级
                            .forEach(e -> { // 层级深度
                                flds.add(e.getValue());
                            }); // 构造字段集合
                    final var node = trie.addParts(flds).flag(false);
                    cons.accept(node, indented_line._2()); // 回调函数
                } // if
            } else {
                System.err.println(line);
            } // if
        } // for
        
        return trie;
    }
    
    /**
     * yml 的格式解析
     * 
     * @param file        文件路径
     * @param start_level 开始层级，从0开始
     * @return TrieNode 根节点的值 为 null
     */
    public TrieNode<String> yml(final String file) {
        final var path = FileSystem.fileOf(file, this.getClass()); // 文件路径
        return yml(FileSystem.utf8lines(path).map(e->BasePair.bp(e, e)),(n,u)->{}); 
    }
    
    /**
     * yml 的格式解析
     * 
     * @param file        文件路径
     * @param start_level 开始层级，从0开始
     * @return TrieNode 根节点的值 为 null
     */
    public TrieNode<String> yml(final File file) {
        return yml(FileSystem.utf8lines(file).map(e->BasePair.bp(e, e)),(n,u)->{}); 
    }
    
    /**
     * yml 的格式解析
     * 
     * @param dfm 文件路径
     * @return TrieNode 根节点的值 为 rootName
     */
    public TrieNode<String> yml(final DFrame dfm) {
        final var lineS = dfm.rowsS(e -> BasePair.bp(e.str(0), e)) // 提取第一列作为结构目录行
                .filter(p -> !p._1().equals("null")); // 去除掉空值行

        return yml(lineS, (node, u) -> {
            node.setAttribute("$infos", u);
        });
    }
    
    public String getRootName() {
        return rootName;
    }

    /**
     * 
     * @param rootName
     * @return
     */
    public YmlEngine setRootName(String rootName) {
        this.rootName = rootName;
        return this;
    }
    
    private int indent_unit = 4;
    private String rootName;
    
}


public class JunitDB4 {
    
    @Test
    public void accounting() {
        final var rb = IRecord.rb("drcr,name");
        final List<IRecord> accounting_policies = L( // accounting policies
                REC("name","持有至到期投资的核算","entries",XVec.OF(
                    1,    "应收利息",
                    -1,    "投资收益",
                    0,    "持有至到期投资－利息调整",
                    0,    FinanceAccounting.DRCR_ADJUST
                ).sliceX(rb::get,2) // entries
            ) // REC
        ); // accounting_policies
        
        final var policies = accounting_policies.stream()
                .map(e -> BasePair.bp(e.str("name"), e.recs("entries")))
                .collect(PVec.pvec2clc()).toHashMap();
        
        println(policies.get("持有至到期投资的核算"));
        
        final var accounting_method = new FinanceAccounting().double_entries(accounting_policies);
        
        println("-----------------ACCOUNTING ENTRY POSTING-----------------------");
        XVec.OF1( // session data
            "持有至到期投资的核算",REC("应收利息",25,"投资收益",-28),
            "持有至到期投资的核算",REC("应收利息",25,"投资收益",-25),
            "持有至到期投资的核算",REC("应收利息",35,"投资收益",-25)
        ).sliceX(BasePair::bptt,2).cast(BasePair.bp("", REC()))
         .forEach(e -> {
            println("--");
            accounting_method.apply(e._1(),e._2())
                .forEach(r->{
                    println(r);
                }); // forEach
        });// forEach
    }
    
    @Test
    public void accounting2() {
        final var fiance =  new FinanceAccounting();
        final var file = FileSystem.path("data/acct_policies.txt", getClass());
        final var accounting_policies = fiance.policies(file);
        final var accounting_method = fiance.double_entries(accounting_policies);
        
        println("-----------------ACCOUNTING ENTRY POSTING-----------------------");
        XVec.OF1( // session data
            "持有至到期投资的核算",REC("应收利息",25,"投资收益",-28),
            "持有至到期投资的核算",REC("应收利息",25,"投资收益",-25),
            "持有至到期投资的核算",REC("应收利息",35,"投资收益",-25),
            "销售商品",REC("库存现金",35,"主营业务收入",-25)
        ).sliceX(BasePair::bptt,2)
        .cast(BasePair.bp("", REC()))
        .forEach(e -> {
            println("--");
            accounting_method.apply(e._1(),e._2())
                .forEach(r->{
                    println(r);
                }); // forEach
        });// forEach
    }
    
    /**
     * 处方
     */
    @Test
    public void prescriptions() {
        final var home = FileSystem.fileOf("data/prescriptions", getClass());
        
        final Function<File, TrieNode<String>> parse_file = (file) -> {
            final var root = new YmlEngine().setRootName("root").yml(file);
            return root;
        };

        FileSystem.tranverse(home, file -> {
            final var trienode = parse_file.apply(file);
            final var node = trienode.pathOf("root/长期医嘱".split("/"));
            final var mm = node.pathOf("长期医嘱/用药医嘱/or".split("/"));
            mm.childrenS().forEach(t->{
                println(t,t.childrenL());
            });
        });
    }

}
